import { computed, onMounted, onUnmounted, reactive, ref, Ref, watch } from 'vue'
import { queryClosestSelector } from '@/utils'
import { DragBox, DragBoxDefault, DragElement, DragState } from './type'

/** 盒子状态 */
export default function useState(
  data: Ref<Partial<DragBox>>,
  el: Ref<DragElement | null>,
  parent: Ref<boolean | string>,
  resizeParent: Ref<boolean>,
  resizeAtCenter: Ref<boolean>
) {
  const box = useBox(data)

  /** 更新节点状态 */
  function update() {
    const { x, y } = box

    Object.assign(state, { x, y })

    const element = el.value

    if (!element) return

    const { offsetWidth: w, offsetHeight: h } = element

    Object.assign(state, { w, h })

    // 拖拽范围父节点
    const parentEl =
      typeof parent.value === 'string' ? queryClosestSelector(element, parent.value) : element.parentElement

    if (!parentEl || !resizeParent.value) {
      Object.assign(state, {
        pw: Infinity,
        ph: Infinity
      })

      return
    }

    const { offsetWidth: pw, offsetHeight: ph } = parentEl

    Object.assign(state, { pw, ph })
  }

  /** 状态 */
  const state: DragState = reactive({
    active: useActive(el),
    parent,
    resizeParent,
    resizeAtCenter,
    w: 0,
    h: 0,
    x: 0,
    y: 0,
    pw: Infinity,
    ph: Infinity,
    update
  })

  /** 节点变更则更新 */
  watch(el, update)

  return { box, state }
}

/** 盒子数据，用以代理 props 数据 */
export function useBox(data: Ref<Partial<DragBox>>) {
  // 允许改变的字段
  const changeableKeys = ['w', 'h', 'x', 'y']
  // 代理值，如果未设置则使用默认值
  const proxyValue = (key: keyof DragBox) => data.value[key] ?? DragBoxDefault[key]

  /** 盒子数据 */
  const box: DragBox = reactive(
    Object.keys(DragBoxDefault).reduce((total, key) => {
      // 可改变的值拷贝一份单独维护
      // 无需改变的则直接从组件选项 get 获取
      total[key] = changeableKeys.includes(key)
        ? proxyValue(key as keyof DragBox)
        : computed(() => proxyValue(key as keyof DragBox))

      return total
    }, Object.create(null))
  )

  // 组件选项更改则同步更新盒子数据
  changeableKeys.forEach(key =>
    watch(
      () => data.value[key as keyof DragBox],
      val => {
        box[key as keyof DragBox] = val as number
      }
    )
  )

  return box
}

/** 激活 */
export function useActive(
  /** 元素节点 */
  el: Ref<DragElement | null>
) {
  const active = ref(false)

  /** 检测是否激活 */
  function checkActive(e: MouseEvent) {
    active.value = !!(e.target && el.value?.contains(e.target as Node))
  }

  onMounted(() => document.addEventListener('click', checkActive))
  onUnmounted(() => document.removeEventListener('click', checkActive))

  return active
}
